package com.glg.javadoc_searcher;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Parser {
    // 先指定一个加载文档的路径

    private static final String INPUT_PATH = "/Users/yechiel/Desktop/Byte/code_world/docs";

    // 创建一个 Index 实例
    private Index index = new Index();

    // 通过这个方法实现“单线程索引”
    public void run(){
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始！");
        // 整个 Parser 的入口
        // 1. 根据指定的路径，枚举出该路径中所有的文件(HTML)，这个过程需要把所有子目录中的文件都获取到
        ArrayList<File> fileList = new ArrayList<>();
        enumFile(INPUT_PATH, fileList);
        /*for(File file : fileList){
            System.out.println(file);
        }
        System.out.println(fileList.size());
        */

        // 2. 针对上面罗列出的文件路径，打开路径，读取文件内容，进行解析，并构建索引
        for(File f : fileList) {
            // 通过这个方法来解析单个 HTML 文件
            System.out.println("开始解析： " + f.getAbsolutePath());
            parseHTML(f);
        }

        // 3. 把在内存中构造好的索引数据结构，保存到指定的文件中
        index.save();

        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕！消耗时间为：" + (end - beg) + "ms");
    }


    // 通过这个方法实现“多线制作索引”
    public void runByThread() throws InterruptedException {
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始！");

        // 1. 枚举出所有的文件
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH, files);

        // 2. 循环遍历文件（多线程，引入线程池）
        CountDownLatch latch = new CountDownLatch(files.size()); // 选手（文件）的个数
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        for(File f : files){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    // 打印要解析的文件路径
                    System.out.println("解析 " + f.getAbsolutePath());
                    parseHTML(f);
                    latch.countDown();  // 通知 CountDownLatch，选手已经到达终点，已经撞线
                }
            });
        }
        // 死等。await 方法会阻塞，直到所有的选手都调用 countDown 撞线之后，才会返回
        latch.await();

        // 手动把线程池里面的线程都干掉
        executorService.shutdown();

        // 3. 保存索引
        index.save();


        long end = System.currentTimeMillis();
        System.out.println("索引制作结束！消耗时间：" + (end - beg) + "ms");
    }


    private String readFile(File f) {
        // BufferedReader 设置缓冲区，将 f 中的内容预读到内存中
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f))){
            StringBuilder content = new StringBuilder();
            while(true) {
                int ret = bufferedReader.read();
                if(ret == -1) {
                    // 读完了
                    break;
                }
                char c = (char) ret;
                if (c == '\n' || c == '\r') {
                    c = ' ';
                }
                content.append(c);
            }
            return content.toString();
        }catch (IOException e){
            e.printStackTrace();
        }
        return "";  // 抛了异常，就直接返回一个空字符串
    }


    // 这个方法内部就基于正则表达式，实现去标签，以及去除 script
    public String parseContentByRegex(File f) {
        //1. 先把整个文件都读到 String 里面
        String content = readFile(f);

        // 2. 替换掉 script 标签
        content = content.replaceAll("<script.*?>(.*?)</script>", " ");

        // 3. 替换掉普通的 HTML 标
        content = content.replaceAll("<.*?>", " ");

        // 4. 使用正则把多个空格，合并成一个空格
        content = content.replaceAll("\\s+", " ");
        return content;
    }




    private void parseHTML(File f) {
        // 1. 解析出 HTML 的标题
        String title = parseTitle(f);
        // 2. 解析出 HTML 对应的 URL
        String url = parseUrl(f);
        // 3. 解析出 HTML 对应的正文（有了正文才有后续的描述）
        String content = parseContentByRegex(f);
        // 4. 将解析出来的这些信息，加入到索引当中
        index.addDoc(title,url,content);
    }

    // 用来解析 HTML 里面的标题信息
    private String parseTitle(File f) {
        String name = f.getName();
        return name.substring(0, name.length() - ".html".length());
    }

    // 用来解析 HTML 里面的 URL 信息
    private String parseUrl(File f) {
        String part1 = "https://docs.oracle.com/javase/8/docs/";
        String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());
        return part1 + part2;
    }

    // 用来解析 HTML 里面的正文信息
    public String parseContent(File f) {
        //先按照一个字符一个字符的方式来读取，以 < 和 > 来控制拷贝数据的开关
        StringBuilder content = new StringBuilder();
        // 手动把缓冲区设置成 1M 大小
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f), 1024 * 1024)){
            // 加上一个是否要进行拷贝的开关
            boolean isCopy = true;
            // 还得准备一个保存结果的 StringBuilder
            //StringBuilder content = new StringBuilder();
            while (true) {
                // 注意：此处的 read() 返回值是 int，不是 char
                // 按理说，应该是依次读一个字符，返回 char 就够了呀？
                // 此处使用 int 作为返回值，主要是为了表示一些非法情况
                // 比如说读到了文件末尾，继续读，就会返回 -1
                // 我们就可以根据返回的 -1 判断读完了
                int ret = bufferedReader.read();
                if (ret == -1) {
                    // 表示文件读完了
                    break;
                }
                // 这个结果不是 -1，那么就是一个合法的字符了
                char c = (char) ret;
                if (isCopy) {
                    // 开关打开的状态，遇到普通字符就应该拷贝到 StringBuilder 中
                    if (c == '<') {
                        // 关闭开关
                        isCopy = false;
                        continue;
                    }
                    if (c == '\n' || c == '\r') {
                        // 为了去掉换行，把换行/回车替换成空格
                        c = ' ';
                    }
                    // 其他字符，直接进行拷贝即可，把结果拷贝到最终的 StringBuilder 中
                    content.append(c);
                } else {
                    // 开关关闭的状态，暂时不拷贝，直到遇到 >
                    if (c == '>') {
                        isCopy = true;
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return content.toString();
    }




    // 第一个参数表示我们从哪个参数开始进行递归遍历
    // 第二个参数表示递归得到的结果
    private void enumFile(String inputPath, ArrayList<File> fileList) {
        File rootPath = new File(inputPath);
        // 把当前目录中，所包含的目录名全部获取到
        // listFiles 能够获取到 rootPath 当前目录下所包含的文件/目录（一层目录，不会进入子文件）
        File[] files = rootPath.listFiles();
        for(File f : files) {
            // 此时我们就根据当前 f 的类型，来决定是否要进行递归
            // 若 f 是一个普通文件，就把 f 加入到 fileList 结果中
            // 若 f 是一个目录，就递归调用 enumFile 方法，来进一步地获取子目录中的内容
            if(f.isDirectory()) {
                enumFile(f.getAbsolutePath(),fileList);
            }else {
                if (f.getAbsolutePath().endsWith(".html"))
                    fileList.add(f);
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        // 通过 main 方法，来实现整个制作索引的过程
        Parser parser = new Parser();
        //parser.runByThread();
        parser.runByThread();
    }
}
